package com.insight.common.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import com.insight.common.api.dto.LogDTO;
import com.insight.common.api.vo.Result;
import com.insight.common.aspect.annotation.AutoLog;
import com.insight.common.constant.CommonConstant;
import com.insight.common.constant.enums.ModuleType;
import com.insight.common.system.vo.LoginUser;
import com.insight.common.util.IPUtils;
import com.insight.common.util.SpringContextUtils;
import com.insight.common.util.oConvertUtils;
import com.insight.modules.base.service.BaseCommonService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;


/**
 * 系统日志，切面处理类
 *
 * @Author scott
 * @email jeecgos@163.com
 * @Date 2018年1月14日
 */
@Aspect
@Component
@ConditionalOnProperty(name = "yuanqiao.core.aspect", havingValue = "true")
public class AutoLogAspect {

    @Resource
    private BaseCommonService baseCommonService;

    /**
     * 日志切点，匹配带有@AutoLog注解的方法。
     */
    @Pointcut("@annotation(com.insight.common.aspect.annotation.AutoLog)")
    public void logPointCut() {

    }

    /**
     * 环绕通知，记录方法执行时间并保存日志。
     * @param point 切点
     * @return 方法执行结果
     * @throws Throwable 异常
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;

        //保存日志
        saveSysLog(point, time, result);

        return result;
    }

    /**
     * 保存系统日志到数据库。
     * @param joinPoint 切点
     * @param time 执行耗时
     * @param obj 方法返回结果
     */
    private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        LogDTO dto = new LogDTO();
        AutoLog syslog = method.getAnnotation(AutoLog.class);
        if (syslog != null) {
            //update-begin-author:taoyan date:
            String content = syslog.value();
            if (syslog.module() == ModuleType.ONLINE) {
                content = getOnlineLogContent(obj, content);
            }
            //注解上的描述,操作日志内容
            dto.setLogType(syslog.logType());
            dto.setLogContent(content);
        }

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        dto.setMethod(className + "." + methodName + "()");

        //获取request
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        //请求的参数
        dto.setRequestParam(getReqestParams(request, joinPoint));
        //设置操作类型
        if (dto.getLogType() == CommonConstant.LOG_TYPE_2) {
            dto.setOperateType(getOperateType(methodName, syslog.operateType()));
        }else if(dto.getLogType() == CommonConstant.LOG_TYPE_3){
            JSONObject jsonObject = JSON.parseArray(dto.getRequestParam()).getJSONObject(0);
            String username = jsonObject.getString("username");
            LoginUser loginUser = baseCommonService.selectUserByUsername(username);
            dto.setUserid(username);
            dto.setUsername(loginUser.getRealname());
        }

        //设置IP地址
        dto.setIp(IPUtils.getIpAddr(request));
        //获取登录用户信息
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (sysUser != null) {
            dto.setUserid(sysUser.getUsername());
            dto.setUsername(sysUser.getRealname());

        }
        //耗时
        dto.setCostTime(time);
        dto.setCreateTime(new Date());
        if (ObjectUtils.isNotEmpty(obj) && obj instanceof Result) {
            JSONObject jsonObject = JSON.parseObject(JSONObject.toJSONString(obj));
            String jsonString = jsonObject.toJSONString();
            if (jsonString.length() > 60000) {
                jsonString = jsonString.substring(0, 60000);
            }
            dto.setResponseResult(jsonString);
            dto.setOperateResult(jsonObject.getBoolean("success") ? "成功" : "失败");
        }else {
            dto.setOperateResult("成功");
        }
        //保存系统日志
        baseCommonService.addLog(dto);
    }


    /**
     * 获取操作类型（如查询、添加、修改、删除等）。
     * @param methodName 方法名
     * @param operateType 注解指定操作类型
     * @return 操作类型编号
     */
    private int getOperateType(String methodName, int operateType) {
        if (operateType > 0) {
            return operateType;
        }
        if (methodName.startsWith("list")
                || methodName.startsWith("query")
                || methodName.startsWith("find")
                || methodName.startsWith("get")) {
            return CommonConstant.OPERATE_TYPE_1;
        }
        if (methodName.startsWith("add")
                || methodName.startsWith("save")) {
            return CommonConstant.OPERATE_TYPE_2;
        }
        if (methodName.startsWith("edit")
                || methodName.startsWith("update")) {
            return CommonConstant.OPERATE_TYPE_3;
        }
        if (methodName.startsWith("delete")
                || methodName.startsWith("remove")) {
            return CommonConstant.OPERATE_TYPE_4;
        }
        if (methodName.startsWith("import")) {
            return CommonConstant.OPERATE_TYPE_5;
        }
        if (methodName.startsWith("export")) {
            return CommonConstant.OPERATE_TYPE_6;
        }
        return CommonConstant.OPERATE_TYPE_1;
    }

    /**
     * 获取请求参数字符串。
     * @param request 请求对象
     * @param joinPoint 切点
     * @return 参数字符串
     */
    private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) {
        String httpMethod = request.getMethod();
        String params = "";
        if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod)) {
            Object[] paramsArray = joinPoint.getArgs();
            // java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
            //  https://my.oschina.net/mengzhang6/blog/2395893
            Object[] arguments = new Object[paramsArray.length];
            for (int i = 0; i < paramsArray.length; i++) {
                if (paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
                    //ServletRequest不能序列化，从入参里排除，否则报异常：java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
                    //ServletResponse不能序列化 从入参里排除，否则报异常：java.lang.IllegalStateException: getOutputStream() has already been called for this response
                    continue;
                }
                arguments[i] = paramsArray[i];
            }
            //update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
            PropertyFilter profilter = (o, name, value) -> value == null || value.toString().length() <= 500;
            params = JSONObject.toJSONString(arguments, profilter);
            //update-end-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
        } else {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            // 请求的方法参数值
            Object[] args = joinPoint.getArgs();
            // 请求的方法参数名称
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);
            if (args != null && paramNames != null) {
                for (int i = 0; i < args.length; i++) {
                    params += "  " + paramNames[i] + ": " + args[i];
                }
            }
        }
        return params;
    }

    /**
     * 拼接online日志内容。
     * @param obj 返回对象
     * @param content 日志内容
     * @return 拼接后的日志内容
     */
    private String getOnlineLogContent(Object obj, String content) {
        if (Result.class.isInstance(obj)) {
            Result res = (Result) obj;
            String msg = res.getMessage();
            String tableName = res.getOnlTable();
            if (oConvertUtils.isNotEmpty(tableName)) {
                content += ",表名:" + tableName;
            }
            if (res.isSuccess()) {
                content += "," + (oConvertUtils.isEmpty(msg) ? "操作成功" : msg);
            } else {
                content += "," + (oConvertUtils.isEmpty(msg) ? "操作失败" : msg);
            }
        }
        return content;
    }


    /*    private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        SysLog sysLog = new SysLog();
        AutoLog syslog = method.getAnnotation(AutoLog.class);
        if(syslog != null){
            //update-begin-author:taoyan date:
            String content = syslog.value();
            if(syslog.module()== ModuleType.ONLINE){
                content = getOnlineLogContent(obj, content);
            }
            //注解上的描述,操作日志内容
            sysLog.setLogContent(content);
            sysLog.setLogType(syslog.logType());
        }

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");


        //设置操作类型
        if (sysLog.getLogType() == CommonConstant.LOG_TYPE_2) {
            sysLog.setOperateType(getOperateType(methodName, syslog.operateType()));
        }

        //获取request
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        //请求的参数
        sysLog.setRequestParam(getReqestParams(request,joinPoint));

        //设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));

        //获取登录用户信息
        LoginUser sysUser = (LoginUser)SecurityUtils.getSubject().getPrincipal();
        if(sysUser!=null){
            sysLog.setUserid(sysUser.getUsername());
            sysLog.setUsername(sysUser.getRealname());

        }
        //耗时
        sysLog.setCostTime(time);
        sysLog.setCreateTime(new Date());
        //保存系统日志
        sysLogService.save(sysLog);
    }*/
}
